using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
using Mirror;
using Mirror.Discovery;

namespace Shows_3D
{
    [Serializable]
    public class ServerFoundUnityEvent : UnityEvent<Shows3DServerResponse>
    {
    };

    [Serializable]
    public class ServerOfflineUnityEvent : UnityEvent<ServerOfflineMessage>
    {
    };

    [DisallowMultipleComponent]
    [AddComponentMenu("Network/NetworkDiscovery")]
    public class Shows3DNetworkDiscovery : NetworkDiscoveryBase<ServerRequest, Shows3DServerResponse>
    {
        #region Server

        public long ServerId { get; private set; }

        [Tooltip("Transport to be advertised during discovery")]
        public Transport transport;

        [Tooltip("Invoked when a server is found")]
        public ServerFoundUnityEvent OnServerFound;

        public ServerOfflineUnityEvent OnServerOffline;

        [Tooltip("客户端用来接收服务器消息，比如下线消息")] [SerializeField]
        private int clientReceivePort = 47778;

        public override void Start()
        {
            ServerId = RandomLong();

            // active transport gets initialized in awake
            // so make sure we set it here in Start()  (after awakes)
            // Or just let the user assign it in the inspector
            if (transport == null)
                transport = Transport.activeTransport;

            base.Start();
        }

        /// <summary>
        /// Process the request from a client
        /// </summary>
        /// <remarks>
        /// Override if you wish to provide more information to the clients
        /// such as the name of the host player
        /// </remarks>
        /// <param name="request">Request comming from client</param>
        /// <param name="endpoint">Address of the client that sent the request</param>
        /// <returns>The message to be sent back to the client or null</returns>
        protected override Shows3DServerResponse ProcessRequest(ServerRequest request, IPEndPoint endpoint)
        {
            // In this case we don't do anything with the request
            // but other discovery implementations might want to use the data
            // in there,  This way the client can ask for
            // specific game mode or something

            try
            {
                // this is an example reply message,  return your own
                // to include whatever is relevant for your game
                return new Shows3DServerResponse
                {
                    serverId = ServerId,
                    uri = transport.ServerUri(),
                    name = "guanfei"
                };
            }
            catch (NotImplementedException)
            {
                Debug.LogError($"Transport {transport} does not support network discovery");
                throw;
            }
        }

        #endregion

        #region Client

        /// <summary>
        /// Create a message that will be broadcasted on the network to discover servers
        /// </summary>
        /// <remarks>
        /// Override if you wish to include additional data in the discovery message
        /// such as desired game mode, language, difficulty, etc... </remarks>
        /// <returns>An instance of ServerRequest with data to be broadcasted</returns>
        protected override ServerRequest GetRequest() => new ServerRequest();

        /// <summary>
        /// Process the answer from a server
        /// </summary>
        /// <remarks>
        /// A client receives a reply from a server, this method processes the
        /// reply and raises an event
        /// </remarks>
        /// <param name="response">Response that came from the server</param>
        /// <param name="endpoint">Address of the server that replied</param>
        protected override void ProcessResponse(Shows3DServerResponse response, IPEndPoint endpoint)
        {
            // we received a message from the remote endpoint
            response.EndPoint = endpoint;

            // although we got a supposedly valid url, we may not be able to resolve
            // the provided host
            // However we know the real ip address of the server because we just
            // received a packet from it,  so use that as host.
            UriBuilder realUri = new UriBuilder(response.uri)
            {
                Host = response.EndPoint.Address.ToString()
            };
            response.uri = realUri.Uri;

            OnServerFound.Invoke(response);
        }

        #endregion

        public void UnAdvertiseServer()
        {
            serverUdpClient?.Close();
        }


        public void BroadcastStopHostRequest()
        {
            if (serverUdpClient == null) return;
        
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast,clientReceivePort);
        
            ServerOfflineMessage message = new ServerOfflineMessage
            {
                serverId = ServerId
            };
        
            using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
            {
                try
                {
                    writer.Write(message);
                    ArraySegment<byte> data = writer.ToArraySegment();
                    serverUdpClient.Send(data.Array, data.Count, endPoint);
                }
                catch (Exception)
                {
                    //  
                }
            }
        }

        public async Task ClientListenMessageAsync()
        {
            while (true)
            {
                try
                {
                    Debug.Log("ListenServerRequestAsync!!!!!!!!!!!!!!!!");
                    await ListenServerRequestAsync(clientUdpClient);
                }
                catch (ObjectDisposedException)
                {
                    // socket has been closed
                    break;
                }
                catch (Exception)
                {
                }
            }
        }

        private async Task ListenServerRequestAsync(UdpClient udpClient)
        {
            UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync();
        
            using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(udpReceiveResult.Buffer))
            {
                if (networkReader.ReadInt64() != secretHandshake)
                    return;
        
                ServerOfflineMessage response = networkReader.Read<ServerOfflineMessage>();
        
                Debug.Log("offline!!!!!!!!!!!!!!!!");
                Debug.Log(response.serverId + "   offline!!!!!!!!!!!!!!!!");
                OnServerOffline.Invoke(response);
            }
        }
    }
}